<?php

/**
 * mForge Framework
 *
 * LICENSE:
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * The license is bundled with this package in the file LICENSE.txt.
 * You may also obtain a copy of the License at this URL:
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @copyright  Copyright (c) 2011, Jonathan Hedrén
 * @author     Jonathan Hedrén <jonathan.hedren@gmail.com>
 * @license    http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
 */

namespace mForge\Modules\Session;

class Session
{
    const SESSION_FINGER_PRINT_KEY = 'session_fingerprint_key';

    /**
     * Indicates if the session is destroyed or not.
     *
     * @var boolean
     */
    private $destroyed = false;

    /**
     * The finger print key is used to validate the session.
     *
     * @var string
     */
    protected $fingerPrintKey;

    /**
     * Indicates if the session is started or not.
     *
     * @var boolean
     */
    private $started = false;


    /**
     * Starts the session.
     *
     * @param string $name
     * @param string $fingerPrintKey
     * @param string $savePath
     * @return void
     */
    public function __construct($name = null, $fingerPrintKey = null, $savePath = null)
    {
        if ($this->started) {
            return;
        }
        if ($savePath !== null) {
            $this->setSavePath($savePath);
        }

        ini_set('session.use_cookies', 1);
        ini_set('session.use_only_cookies', 1);

        if ($name !== null) {
            $this->setName($name);
        }

        $this->setFingerPrintKey($fingerPrintKey);

        session_start();
        $this->started = true;

        $this->fingerPrint();
    }

    /**
     * Destroys the session.
     *
     * @return void
     */
    public function destroy($expireCookie = true)
    {
        if ($this->isDestroyed()) {
            return;
        }

        session_destroy();

        if ($expireCookie && isset($_COOKIE[session_name()])) {
            $cookieParams = session_get_cookie_params();
            setcookie(
                session_name(),
                false,
                -3600, // strtotime('1980-01-01'),
                $cookieParams['path'],
                $cookieParams['domain'],
                $cookieParams['secure']
            );
        }

        $this->destroyed = true;
    }

    /**
     * Sets a finger print to the session. The finger print is used to check if
     * the session is valid.
     *
     * @return void
     */
    public function fingerPrint()
    {
        $this->set(self::SESSION_FINGER_PRINT_KEY, $this->generateFingerPrint());
    }

    /**
     * Reads the session. If no key is provided the whole session array will be
     * returned.
     *
     * @param string $key
     * @return mixed
     */
    public function get($key = null)
    {
        if (!$this->isStarted()) {
            throw new \Exception('To get anything from the session, it needs to be started.');
        }
        if ($key === null) {
            return $_SESSION;
        }

        if (isset($_SESSION[$key])) {
            return $_SESSION[$key];
        }
        return null;
    }

    /**
     * Generates a finger print for session validation.
     *
     * @return string
     */
    private function generateFingerPrint()
    {
        $userAgent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
        return md5($this->fingerPrintKey . $userAgent . $this->getId());
    }

    /**
     * Returns the session id.
     *
     * @return string
     */
    public function getId()
    {
        return session_id();
    }

    /**
     * Returns the name of the session.
     *
     * @return string
     */
    public function getName()
    {
        return session_name();
    }

    /**
     * Returns the session save path.
     *
     * @return string
     */
    public function getSavePath()
    {
        return session_save_path();
    }

    /**
     * Checks if the session is destroyed.
     *
     * @return boolean
     */
    public function isDestroyed()
    {
        return $this->destroyed;
    }

    /**
     * Checks if the session is started.
     *
     * @return boolean
     */
    public function isStarted()
    {
        return $this->started;
    }

    /**
     * Checks if the session is valid.
     *
     * @return boolean
     */
    public function isValid()
    {
        if ($this->get(self::SESSION_FINGER_PRINT_KEY) == $this->generateFingerPrint()) {
            return true;
        }
        return false;
    }

    /**
     * Regenerates the session id.
     *
     * @return void
     */
    public function regenerateId()
    {
        session_regenerate_id(true);
        $this->fingerPrint();
    }

    /**
     * Writes to the session
     *
     * @param string $key
     * @param mixed $value
     * @return void
     */
    public function set($key, $value)
    {
        if (!$this->isStarted()) {
            throw new \Exception('To set something to the session, it needs to be started.');
        }
        $_SESSION[$key] = $value;
    }

    /**
     * Sets the finger print key which is used to validate the session.
     *
     * @param string $fingerPrintKey
     * @return void
     */
    public function setFingerPrintKey($fingerPrintKey)
    {
        if ($this->isStarted()) {
            throw new \Exception('The finger print key must be set before the session is started.');
        }
        $this->fingerPrintKey = $fingerPrintKey;
    }

    /**
     * Sets a specified id as session id.
     *
     * @param string $id
     * @return void
     */
    public function setId($id)
    {
        if ($this->isStarted()) {
            throw new \Exception('The session id must be set before the session is started.');
        }
        if (!is_string($id) || $id === '') {
            throw new \Exception('Session id must be a string that is not empty.');
        }
        session_id($id);
    }

    /**
     * Sets the session name.
     *
     * @param string $name
     * @return void
     */
    public function setName($name)
    {
        if ($this->isStarted()) {
            throw new \Exception('The session name must be set before the session is started.');
        }
        //TODO: ensure that $name is a valid session name
        session_name($name);
    }

    /**
     * Sets a custom save handler.
     *
     * @param array|string $open
     * @param array|string $close
     * @param array|string $read
     * @param array|string $write
     * @param array|string $destroy
     * @param array|string $gc
     * @return void
     */
    public function setSaveHandler($open, $close, $read, $write, $destroy, $gc)
    {
        session_set_save_handler($open, $close, $read, $write, $destroy, $gc);
    }

    /**
     * Sets the session save path.
     *
     * @param string $savePath
     * @return void;
     */
    public function setSavePath($savePath)
    {
        if ($this->isStarted()) {
            throw new \Exception('The session save path must be set before the session is started.');
        }
        session_save_path($savePath);
    }
}